using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.Pkcs;
using System.IO;
using System.Xml;
using Framework.Library.Process;

namespace Framework.Library.Process
{
    class ProcessHelper
    {
        private static string strFileSignatureLocation;

        #region Sign And Encrypt File

        #region Sign And Verify Sign
        public static void SignXMLFile(ref smsObject pkiObject, string strSignatureLocation)
        {

            try
            {
                strFileSignatureLocation = strSignatureLocation;

                if (!pkiObject.IsPKIEmail)
                {
                    string strPathFileSigned = pkiObject.PathOfFile;

                    CreateSigningFile(ref strPathFileSigned);

                    pkiObject.SignatureFile = strPathFileSigned;
                }
                else
                {
                    CreateEmailSigningFile(ref pkiObject);
                }

                X509Certificate2 signer = new X509Certificate2(pkiObject.PFXFile, pkiObject.PFXPassword);

                RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)signer.PrivateKey;
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.PreserveWhitespace = true;
                xmlDoc.Load(pkiObject.SignatureFile);
                SignXml(xmlDoc, rsaKey);
                xmlDoc.GetElementsByTagName("Data2Sign").Item(0).InnerText = null;
                xmlDoc.Save(pkiObject.SignatureFile);


            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            finally
            {
            }


        }

        public static bool VerifySign(smsObject pkiObject, string strPathOfCertFile)
        {
            bool result = false;
            try
            {
                X509Certificate2 x509Signer = new X509Certificate2(strPathOfCertFile);
                RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
                rsaKey.FromXmlString(x509Signer.PublicKey.Key.ToXmlString(false));
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.PreserveWhitespace = true;
                xmlDoc.Load(pkiObject.SignatureFile);

                byte[] file = null;

                if (!pkiObject.IsPKIEmail)
                {
                    // read input file
                    using (BinaryReader reader = new BinaryReader(new FileStream(pkiObject.PathOfFile, FileMode.Open, FileAccess.Read, FileShare.Read)))
                    {
                        file = new byte[reader.BaseStream.Length];
                        reader.Read(file, 0, file.Length);
                    }
                }
                else
                {
                    file = Encoding.ASCII.GetBytes(pkiObject.EmailBody);
                }

                xmlDoc.GetElementsByTagName("Data2Sign").Item(0).InnerText = Convert.ToBase64String(file);
        
                result = VerifyXml(xmlDoc, rsaKey);
          
            }
            catch (Exception ex)
            {
                throw new Exception("VerifySign: " + ex.Message);
            }
            return result;
        }

        private static bool VerifyXml(XmlDocument doc, RSA key)
        {
            bool result = false;
            try
            {
                if (doc == null)
                    throw new ArgumentException("Doc");
                if (key == null)
                    throw new ArgumentException("Key");
                SignedXml signedXml = new SignedXml(doc);
                XmlNodeList nodeList = doc.GetElementsByTagName("Signature");
                if (nodeList.Count <= 0)
                    throw new CryptographicException("Verifycation failed: No signature was found in the document");

                if (nodeList.Count >= 2)
                    throw new CryptographicException("Verifycation failed: More than one signature was found for the document");

                signedXml.LoadXml((XmlElement)nodeList[0]);

                result = signedXml.CheckSignature(key);
            }
            catch (Exception ex)
            {
                throw new Exception("VerifyXml: " + ex.Message);
            }
            return result;
        }

        private static void CreateSigningFile(ref string strPathFile)
        {
            try
            {
                Guid gFile = new Guid();
                gFile = Guid.NewGuid();

                byte[] arrByte = null;
                using (BinaryReader reader = new BinaryReader(new FileStream(strPathFile, FileMode.Open, FileAccess.Read, FileShare.Read)))
                {
                    arrByte = new byte[reader.BaseStream.Length];
                    reader.Read(arrByte, 0, arrByte.Length);
                }

                strPathFile = gFile.ToString();
                strPathFile = Path.Combine(strFileSignatureLocation, strPathFile);
                strPathFile = strPathFile + ".xml";

                if (!File.Exists(strPathFile))
                {
                    XmlTextWriter tw = new XmlTextWriter(strPathFile, null);

                    tw.Formatting = Formatting.Indented;
                    tw.WriteStartDocument();

                    tw.WriteStartElement("Root");
                    tw.WriteElementString("Data2Sign", null);
                    tw.WriteEndElement();
                    tw.WriteEndDocument();

                    tw.Flush();
                    tw.Close();
                }

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(strPathFile);
                xmlDoc.GetElementsByTagName("Data2Sign").Item(0).InnerText = Convert.ToBase64String(arrByte);
                xmlDoc.Save(strPathFile);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        private static void SignXml(XmlDocument doc, RSA key)
        {
            try
            {
                if (doc == null)
                    throw new ArgumentException("Doc");
                if (key == null)
                    throw new ArgumentException("Key");
                SignedXml signedXml = new SignedXml(doc);
                signedXml.SigningKey = key;
                Reference reference = new Reference();
                reference.Uri = "";
                XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
                reference.AddTransform(env);
                signedXml.AddReference(reference);
                signedXml.ComputeSignature();
                XmlElement xmlDSignature = signedXml.GetXml();
                doc.DocumentElement.AppendChild(doc.ImportNode(xmlDSignature, true));
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
        #endregion

        #region Encrypt And Decrypt

        public static void SignAndEncrypt(ref smsObject pkiObject, string strRecipientCertFile)
        {
            try
            {
                // create temp file name
                string tempFile = Path.GetTempFileName();
                byte[] file = null;

                if (!pkiObject.IsPKIEmail)
                {

                    // read input file
                    using (BinaryReader reader = new BinaryReader(new FileStream(pkiObject.PathOfFile, FileMode.Open, FileAccess.Read, FileShare.Read)))
                    {
                        file = new byte[reader.BaseStream.Length];
                        reader.Read(file, 0, file.Length);
                    }
                }
                else
                {
                    file = Encoding.ASCII.GetBytes(pkiObject.EmailBody);
                }

                // sign file
                X509Certificate2 x509signer = new X509Certificate2(pkiObject.PFXFile, pkiObject.PFXPassword);
                byte[] signedFile = Sign(file, x509signer);

                // encrypt file
                X509Certificate2 x509Recipient = new X509Certificate2(strRecipientCertFile);
                X509Certificate2Collection x509Recipients = new X509Certificate2Collection(x509Recipient);
                byte[] signedAndEncryptedFile = Encrypt(signedFile, x509Recipients);

                if (!pkiObject.IsPKIEmail)
                {
                    // write to temp file
                    using (BinaryWriter writer = new BinaryWriter(new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.None)))
                    {
                        writer.Write(signedAndEncryptedFile);
                    }

                    // swap old and new file
                    File.Delete(pkiObject.PathOfFile);
                    File.Move(tempFile, pkiObject.PathOfFile);
                }
                else
                {
                    pkiObject.EmailBody = Convert.ToBase64String(signedAndEncryptedFile);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("SignAndEncrypt: " + ex.Message);
            }
        }

        private static byte[] Sign(byte[] data, X509Certificate2 signingCert)
        {
            byte[] signedBytes = null;
            try
            {
                // create ContentInfo (what is signed)
                ContentInfo content = new ContentInfo(data);

                // object representing a signed message
                SignedCms signedMessage = new SignedCms(content, false);



                // create CmsSigner (who signs)
                CmsSigner signer = new CmsSigner(signingCert);

                // sign the message
                signedMessage.ComputeSignature(signer);

                // create serialized representation
                signedBytes = signedMessage.Encode();
            }
            catch (Exception ex)
            {
                throw new Exception("Sign: " + ex.Message);
            }

            return signedBytes;
        }

        private static byte[] Encrypt(byte[] data, X509Certificate2Collection encryptingCerts)
        {
            byte[] encryptedBytes = null;
            try
            {
                // create ContentInfo (what is encrypted)
                ContentInfo plainContent = new ContentInfo(data);

                // object representing an encrypted message
                EnvelopedCms encryptedMessage = new EnvelopedCms(plainContent);

                // add recipient
                CmsRecipientCollection recipients = new CmsRecipientCollection(SubjectIdentifierType.IssuerAndSerialNumber, encryptingCerts);

                // encrypt the message
                encryptedMessage.Encrypt(recipients);

                // create serialized representation
                encryptedBytes = encryptedMessage.Encode();
            }
            catch (Exception ex)
            {
                throw new Exception("Encrypt: " + ex.Message);
            }
            return encryptedBytes;
        }



        public static void DecryptAndVerify(ref smsObject pkiObject, string strSignerCertFile)
        {
            try
            {
                string tempFile = string.Empty;

                byte[] file = null;

                if (!pkiObject.IsPKIEmail)
                {
                    // get temp file
                    tempFile = Path.GetTempFileName();

                    // read input file
                    using (BinaryReader reader = new BinaryReader(new FileStream(pkiObject.PathOfFile, FileMode.Open, FileAccess.Read, FileShare.None)))
                    {
                        file = new byte[reader.BaseStream.Length];
                        reader.Read(file, 0, file.Length);
                    }

                }
                else
                {
                    file = Convert.FromBase64String(pkiObject.EmailBody);
                }
                // decrypt

                X509Certificate2 x509 = new X509Certificate2(pkiObject.PFXFile, pkiObject.PFXPassword);
                X509Certificate2Collection x509Collection1 = new X509Certificate2Collection(x509);

                byte[] decryptedFile = Decrypt(file, x509Collection1);

                // verify and remove signature
                X509Certificate2 x509Signer = new X509Certificate2(strSignerCertFile);
                X509Certificate2Collection x509Collection2 = new X509Certificate2Collection(x509Signer);

                byte[] decryptedAndVerifiedFile = VerifyAndRemoveSignature(decryptedFile, x509Collection2);

                if (!pkiObject.IsPKIEmail)
                {
                    // write to temp file
                    using (BinaryWriter writer = new BinaryWriter(new FileStream(tempFile, FileMode.Create, FileAccess.Write)))
                    {
                        writer.Write(decryptedAndVerifiedFile);
                    }

                    // swap old and new file
                    File.Delete(pkiObject.PathOfFile);
                    File.Move(tempFile, pkiObject.PathOfFile);
                }
                else
                {
                    pkiObject.EmailBody = Convert.ToString(decryptedAndVerifiedFile);
                }
            }
            catch (Exception ex)
            {
                throw new Exception("DecryptAndVerify : " + ex.Message);
            }
        }

        private static byte[] Decrypt(byte[] data, X509Certificate2Collection x509Collection)
        {
            byte[] buffer = null;
            try
            {
                // create an object representing an encrypted message and deserialize the byte array
                EnvelopedCms encryptedMessage = new EnvelopedCms();
                encryptedMessage.Decode(data);

                // decrypt the message
                encryptedMessage.Decrypt(x509Collection);
                buffer = encryptedMessage.ContentInfo.Content;
            }
            catch (Exception ex)
            {
                throw new Exception("Decrypt : " + ex.Message);
            }

            return buffer;
        }

        private static byte[] VerifyAndRemoveSignature(byte[] data, X509Certificate2Collection x509Collection)
        {

            byte[] buffer = null;
            try
            {
                // create an object representing a signed message and deserialize the byte array

                SignedCms signedMessage = new SignedCms();
                signedMessage.Decode(data);

                // check the signature
                signedMessage.CheckSignature(x509Collection, false);

                buffer = signedMessage.ContentInfo.Content;
            }
            catch (Exception ex)
            {
                throw new Exception("VerifyAndRemoveSignature : " + ex.Message);
            }

            return buffer;
        }

        #endregion

        #endregion

        #region Sign And Encrypt Email

        private static void CreateEmailSigningFile(ref smsObject pkiObject)
        {
            try
            {
                Guid gFile = new Guid();
                gFile = Guid.NewGuid();

                byte[] arrByte = null;
                arrByte = Encoding.ASCII.GetBytes(pkiObject.EmailBody);

                string strPathFile = gFile.ToString();
                strPathFile = Path.Combine(strFileSignatureLocation, strPathFile);
                strPathFile = strPathFile + ".xml";

                pkiObject.SignatureFile = strPathFile;

                if (!File.Exists(strPathFile))
                {
                    XmlTextWriter tw = new XmlTextWriter(strPathFile, null);

                    tw.Formatting = Formatting.Indented;
                    tw.WriteStartDocument();

                    tw.WriteStartElement("Root");
                    tw.WriteElementString("Data2Sign", null);
                    tw.WriteEndElement();
                    tw.WriteEndDocument();

                    tw.Flush();
                    tw.Close();
                }

                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(strPathFile);
                xmlDoc.GetElementsByTagName("Data2Sign").Item(0).InnerText = Convert.ToBase64String(arrByte);
                xmlDoc.Save(strPathFile);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

        #endregion
    }
}
